[API实践]函数调用与工具使用
本节目标
- 理解函数调用(Function Calling)的概念和工作原理
- 掌握DeepSeek API函数调用的基本语法和参数设置
- 学习如何设计和实现自定义函数
- 通过实例了解函数调用在实际应用中的使用方法
- 构建具有工具使用能力的智能助手
函数调用概述
什么是函数调用
函数调用(Function Calling)是大语言模型的一项重要能力,它允许模型生成结构化的函数调用,从而能够:
- 与外部工具和API交互:如查询天气、执行数据库操作、调用第三方服务等
- 执行特定任务:如日期计算、单位转换、数据分析等
- 返回结构化数据:确保输出符合预定义的格式,便于后续处理
类比理解:如果将AI比作一个智能助手,那么函数调用就像是给这个助手提供了一系列可使用的工具。助手不仅能听懂你的请求,还能选择合适的工具来完成任务,比如使用计算器进行计算,或使用地图应用查询路线。
函数调用的工作流程
函数调用的基本工作流程包括以下步骤:
- 定义函数:开发者预先定义可用的函数及其参数规范
- 用户发起请求:用户提出需要解决的问题
- 模型选择函数:模型根据用户请求,决定是否调用函数以及调用哪个函数
- 生成参数:模型生成符合函数要求的参数
- 执行函数:开发者的应用程序接收函数调用请求,执行相应的函数
- 处理结果:将函数执行结果返回给模型,模型继续对话
DeepSeek API的函数调用
DeepSeek API支持函数调用功能,使用方法与OpenAI的函数调用类似。下面介绍基本用法:
基本语法
python
from openai import OpenAI
def send_messages(messages):
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools
)
return response.choices[0].message
client = OpenAI(
api_key="<your api key>",
base_url="https://api.deepseek.com",
)
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get weather of an location, the user shoud supply a location first",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The city and state, e.g. San Francisco, CA",
}
},
"required": ["location"]
},
}
},
]
messages = [{"role": "user", "content": "How's the weather in Hangzhou?"}]
message = send_messages(messages)
print(f"User>\t {messages[0]['content']}")
tool = message.tool_calls[0]
messages.append(message)
messages.append({"role": "tool", "tool_call_id": tool.id, "content": "24℃"})
message = send_messages(messages)
print(f"Model>\t {message.content}")
function_call参数选项
function_call
参数控制模型如何使用函数,有以下几种选项:
- "auto":让模型自行决定是否调用函数
- {"name": "function_name"}:强制模型调用指定的函数
- "none":禁止模型调用任何函数,即使提供了函数定义
函数定义的关键要素
每个函数定义包含以下核心要素:
- name:函数的唯一标识符,模型将使用它来表示要调用的函数
- description:函数的描述,帮助模型理解何时应该使用该函数
- parameters:函数接受的参数,使用JSON Schema格式定义
- type:参数的数据类型(string, number, boolean, object, array等)
- properties:对象类型参数的属性定义
- required:必填参数列表
- description:参数的详细描述
构建实用的函数调用应用
下面,我们将实现一个包含多个函数的助手,展示函数调用的实际应用。
示例:多功能助手应用
python
import json
import datetime
import requests
import math
from openai import OpenAI
# 初始化客户端
client = OpenAI(api_key="sk-0995c6be9ed14b8ca8293cb7811ce681", base_url="https://api.deepseek.com")
# 定义工具列表
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取指定城市的天气信息",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如'北京'、'上海'等"
},
"date": {
"type": "string",
"description": "查询日期,格式为'YYYY-MM-DD',默认为今天"
}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate_date",
"description": "计算日期,如计算几天后是什么日期,或两个日期之间相隔多少天",
"parameters": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "difference"],
"description": "操作类型:add(增加天数)、subtract(减少天数)、difference(计算差值)"
},
"date": {
"type": "string",
"description": "基准日期,格式为'YYYY-MM-DD',默认为今天"
},
"days": {
"type": "integer",
"description": "天数,用于add和subtract操作"
},
"end_date": {
"type": "string",
"description": "结束日期,用于difference操作,格式为'YYYY-MM-DD'"
}
},
"required": ["operation"]
}
}
},
{
"type": "function",
"function": {
"name": "unit_conversion",
"description": "单位转换,如长度、重量、温度等",
"parameters": {
"type": "object",
"properties": {
"value": {
"type": "number",
"description": "要转换的数值"
},
"from_unit": {
"type": "string",
"description": "原始单位,如'km'、'kg'、'celsius'等"
},
"to_unit": {
"type": "string",
"description": "目标单位,如'm'、'g'、'fahrenheit'等"
}
},
"required": ["value", "from_unit", "to_unit"]
}
}
}
]
# 实现各个函数的具体逻辑
def get_weather(city, date=None):
"""获取天气信息(模拟函数)"""
# 实际应用中应该调用真实的天气API
weather_data = {
"北京": {"temperature": "25°C", "condition": "晴", "humidity": "40%"},
"上海": {"temperature": "28°C", "condition": "多云", "humidity": "65%"},
"广州": {"temperature": "30°C", "condition": "小雨", "humidity": "80%"}
}
if city in weather_data:
result = weather_data[city]
query_date = date if date else datetime.datetime.now().strftime("%Y-%m-%d")
return f"{city}于{query_date}的天气:温度{result['temperature']},{result['condition']},湿度{result['humidity']}"
else:
return f"抱歉,没有找到{city}的天气信息。"
def calculate_date(operation, date=None, days=0, end_date=None):
"""日期计算"""
if date is None:
date = datetime.datetime.now().strftime("%Y-%m-%d")
base_date = datetime.datetime.strptime(date, "%Y-%m-%d")
if operation == "add":
result_date = base_date + datetime.timedelta(days=days)
return f"{date}后{days}天是{result_date.strftime('%Y-%m-%d')}"
elif operation == "subtract":
result_date = base_date - datetime.timedelta(days=days)
return f"{date}前{days}天是{result_date.strftime('%Y-%m-%d')}"
elif operation == "difference":
if end_date is None:
return "计算日期差值需要提供结束日期。"
end = datetime.datetime.strptime(end_date, "%Y-%m-%d")
diff = abs((end - base_date).days)
return f"{date}和{end_date}相差{diff}天。"
else:
return "不支持的操作类型。"
def unit_conversion(value, from_unit, to_unit):
"""单位转换"""
# 定义转换规则
conversions = {
# 长度
"km_to_m": lambda x: x * 1000,
"m_to_km": lambda x: x / 1000,
"m_to_cm": lambda x: x * 100,
"cm_to_m": lambda x: x / 100,
"feet_to_m": lambda x: x * 0.3048,
"m_to_feet": lambda x: x / 0.3048,
# 重量
"kg_to_g": lambda x: x * 1000,
"g_to_kg": lambda x: x / 1000,
"lb_to_kg": lambda x: x * 0.45359237,
"kg_to_lb": lambda x: x / 0.45359237,
# 温度
"celsius_to_fahrenheit": lambda x: x * 9/5 + 32,
"fahrenheit_to_celsius": lambda x: (x - 32) * 5/9
}
key = f"{from_unit.lower()}_to_{to_unit.lower()}"
if key in conversions:
result = conversions[key](value)
return f"{value} {from_unit} = {result} {to_unit}"
else:
return f"不支持从{from_unit}到{to_unit}的转换。"
# 函数调用处理器
def process_tool_call(tool_call):
"""处理工具调用请求,执行相应的函数并返回结果"""
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
if function_name == "get_weather":
result = get_weather(**arguments)
elif function_name == "calculate_date":
result = calculate_date(**arguments)
elif function_name == "unit_conversion":
result = unit_conversion(**arguments)
else:
result = f"未知的函数: {function_name}"
return result, tool_call.id
# 主要对话循环
def chat_with_tools():
"""带有工具调用功能的对话系统"""
messages = []
print("欢迎使用多功能助手!您可以查询天气、计算日期或进行单位转换。\n输入'退出'或'exit'结束对话。")
while True:
user_input = input("\n你: ")
if user_input.lower() in ["退出", "exit", "quit"]:
print("感谢使用,再见!")
break
# 添加用户消息
messages.append({"role": "user", "content": user_input})
# 调用API
response = client.chat.completions.create(
model="deepseek-chat",
messages=messages,
tools=tools
)
# 获取模型的回复
message = response.choices[0].message
# 处理可能的工具调用
if message.tool_calls:
tool_results = []
# 将助手响应添加到消息历史
messages.append(message)
# 处理所有工具调用
for tool_call in message.tool_calls:
# 执行函数并获取结果
result, tool_call_id = process_tool_call(tool_call)
# 将工具调用结果添加到消息历史
messages.append({
"role": "tool",
"tool_call_id": tool_call_id,
"content": result
})
tool_results.append(result)
# 将工具执行结果提供给模型,让模型生成最终回复
second_response = client.chat.completions.create(
model="deepseek-chat",
messages=messages
)
assistant_response = second_response.choices[0].message.content
messages.append({"role": "assistant", "content": assistant_response})
print(f"\nAI: {assistant_response}")
else:
# 没有工具调用,直接显示回复
messages.append({"role": "assistant", "content": message.content})
print(f"\nAI: {message.content}")
if __name__ == "__main__":
chat_with_tools()
运行和测试
将上述代码保存为function_assistant.py
,然后运行:
bash
python function_assistant.py
试试以下几种提问:
- "北京明天的天气如何?"
- "10天后是什么日期?"
- "计算2023-01-01和2023-12-31之间相差多少天"
- "将25摄氏度转换为华氏度"
- "请告诉我5公里等于多少米"
函数设计最佳实践
函数定义原则
- 明确的命名:函数名应直观表达其功能
- 详细的描述:帮助模型正确理解函数用途
- 严格的参数规范:明确定义参数类型、格式和要求
- 合理的参数分组:相关参数应分组在同一个函数中
- 功能单一:每个函数应专注于单一功能,避免过于复杂
常见函数类型
以下是一些常见的函数调用应用场景:
- 信息查询:天气、股票、新闻、维基百科等
- 数据处理:计算、排序、过滤、统计等
- 时间处理:日期计算、时区转换、提醒设置等
- 外部服务集成:电子邮件、日历、笔记、任务管理等
- 数据库操作:查询、插入、更新、删除等
- 自定义分析:文本分析、情感分析、图像分析等
高级函数调用应用
多函数链式调用
更复杂的场景可能需要依次调用多个函数来完成任务:
python
# 行程计划助手示例
functions = [
{
"name": "search_flights",
"description": "搜索两地之间的航班信息",
"parameters": {...}
},
{
"name": "search_hotels",
"description": "搜索目的地的酒店信息",
"parameters": {...}
},
{
"name": "get_weather_forecast",
"description": "获取目的地的天气预报",
"parameters": {...}
},
{
"name": "create_itinerary",
"description": "根据航班、酒店和天气信息创建行程计划",
"parameters": {...}
}
]
在这个例子中,用户可能会提出"帮我规划下周去上海的三天行程",AI会依次调用各个函数,收集必要信息后创建完整行程。
条件函数调用
根据上下文或用户需求动态决定要调用的函数:
python
def determine_functions(user_query):
"""根据用户查询动态确定可用的函数列表"""
all_functions = [weather_function, date_function, conversion_function, ...]
if "天气" in user_query or "温度" in user_query:
return [weather_function]
elif "日期" in user_query or "几天后" in user_query:
return [date_function]
elif "转换" in user_query or "等于" in user_query:
return [conversion_function]
else:
# 默认提供所有函数
return all_functions
处理复杂的函数调用结果
对于复杂的函数调用结果,可能需要特殊处理:
python
def handle_complex_result(function_name, result):
"""处理复杂的函数调用结果"""
if function_name == "data_analysis":
# 可能返回图表数据,需要转换为描述性文本
return convert_chart_to_description(result)
elif function_name == "database_query":
# 可能返回大量数据,需要摘要
return summarize_data(result)
else:
# 普通结果直接返回
return result
错误处理与安全考虑
参数验证
务必在执行函数前验证输入参数的有效性:
python
def validate_and_execute(function_name, arguments):
"""验证参数并执行函数"""
try:
# 基本验证
if function_name == "get_weather":
if "city" not in arguments:
return "错误:缺少必要参数'city'"
# 安全检查:确保city是合法值
allowed_cities = ["北京", "上海", "广州", ...]
if arguments["city"] not in allowed_cities:
return f"错误:不支持的城市'{arguments['city']}'"
# 参数验证通过,执行函数
# ...
except Exception as e:
return f"函数执行错误:{str(e)}"
安全限制
确保函数调用不会导致安全问题:
- 避免执行系统命令:除非绝对必要,否则不要允许AI执行系统命令
- 限制API权限:使用最小权限原则,只授予必要的访问权限
- 添加速率限制:防止过度调用外部API
- 敏感操作确认:对于敏感操作(如发送邮件、修改数据),添加用户确认步骤
本节小结
- 函数调用允许大语言模型与外部工具和API交互
- 函数定义包括名称、描述和参数规范
- 良好的函数设计应遵循明确命名、详细描述和严格参数规范等原则
- 在实际应用中,可以实现多函数协作、条件调用等高级功能
- 安全使用函数调用需要注意参数验证和权限控制
实践任务
- 创建一个包含至少三个不同功能函数的聊天助手
- 实现一个函数,连接外部API(如天气API、新闻API等)
- 尝试设计一个需要多步骤函数调用的复杂任务
- 为上述函数添加安全检查和错误处理机制